'Copyright (c) 2008, William Severance, Jr., WESNet Designs
'All rights reserved.

'Redistribution and use in source and binary forms, with or without modification, are permitted
'provided that the following conditions are met:

'Redistributions of source code must retain the above copyright notice, this list of conditions
'and the following disclaimer.

'Redistributions in binary form must reproduce the above copyright notice, this list of conditions
'and the following disclaimer in the documentation and/or other materials provided with the distribution.

'Neither the name of William Severance, Jr. or WESNet Designs may be used to endorse or promote
'products derived from this software without specific prior written permission.

'Disclaimer: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
'            OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
'            AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE
'            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
'            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
'            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
'            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
'            IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'Although I will try to answer questions regarding the installation and use of this software when
'such questions are submitted via e-mail to the below address, no promise of further
'support or enhancement is made nor should be assumed.

'Developer Contact Information:
'     William Severance, Jr.
'     WESNet Designs
'     679 Upper Ridge Road
'     Bridgton, ME 04009
'     Phone: 207-647-9375
'     E-Mail: bill@wesnetdesigns.com
'     Website: www.wesnetdesigns.com

Imports System
Imports System.ComponentModel
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Drawing
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.IO
Imports System.Xml
Imports System.Reflection
Imports WESNet.AJAX.Controls.Attributes
Imports WESNet.Utilities

#Region "Assembly Resource Attribute - Embedded icons for image buttons and black fill (black.gif)"
<Assembly: System.Web.UI.WebResource("ContrastDown.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("ContrastUp.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("Darken.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("FlipHoriz.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("FlipVert.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("Lighten.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("SelectFile.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("RotateLeft.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("RotateRight.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("SaturationDown.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("SaturationUp.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("SaveImage.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("SelectUpload.jpg", "image/jpg")> 
<Assembly: System.Web.UI.WebResource("black.gif", "image/gif")> 
#End Region

#Region "Assembly Resource Attribute - Embedded css style sheet for cropper"
<Assembly: System.Web.UI.WebResource("WESNetImageEditor.css", "text/css")> 
#End Region

#Region "Assembly Resource Attribute - Embedded wait animation image"
<Assembly: System.Web.UI.WebResource("loading.gif", "image/gif")> 
#End Region

#Region "Assembly Resource Attribute - Client Scripts"
<Assembly: System.Web.UI.WebResource("ImageEditorBehavior.js", "text/javascript")> 
#End Region

Namespace WESNet.AJAX.Controls.ImageEditor

#Region "Enums"
    Public Enum CachePersistenceType As Integer
        Memory = 0
        File = 1
        MemoryFile = 2
    End Enum
#End Region

    <ParseChildren(True), PersistChildren(False)> _
    <Designer(GetType(WESNet.AJAX.Controls.ImageEditor.ImageEditorDesigner))> _
    <ToolboxData("<{0}:ImageEditor runat=""server"" />")> _
    Public Class ImageEditor
        Inherits System.Web.UI.ScriptControl
        Implements INamingContainer
        Implements ICallbackEventHandler
        Implements IPostBackDataHandler
        Implements IPostBackEventHandler

#Region "Private Members"
        'Child Controls
        Private _ScriptManager As System.Web.UI.ScriptManager
        Private _CaptionBar As HtmlControls.HtmlGenericControl
        Private _FilenameBar As HtmlControls.HtmlGenericControl
        Private _imgPreview As HtmlControls.HtmlImage
        Private _ToolbarButtons As Generic.List(Of ToolBarButton)
        Private _SelectFileButton As ToolBarButton = Nothing
        Private _SelectUploadButton As ToolBarButton = Nothing
        Private _SaveImageButton As ToolBarButton = Nothing
        'Variables
        Private _ToolbarIconPath As String = String.Empty
        Private _ShowFilename As Boolean = True
        Private _ImageGuid As String = Nothing
        Private _CacheKeyPrefix As String = Defaults.CacheKeyPrefix
        Private _CacheTime As TimeSpan = TimeSpan.FromSeconds(Defaults.CacheTime)
        Private _Image As System.Drawing.Bitmap
        Private _CallbackResult As String
        Private _ClientStateFieldID As String
        Private _ClientState As Collections.Specialized.HybridDictionary
        'Private _UpdateCaptionScript As String
        'Private _UpdateFilenameScript As String
#End Region

#Region "Events / Delegates"
        Public Delegate Sub ImageEditorEventHandler(ByVal source As Object, ByVal e As ImageEditorEventArgs)
        Public Event Command As CommandEventHandler
        Public Event DirtyChanged As System.EventHandler
        Public Event SaveImage As ImageEditorEventHandler
        Public Event Reload As ImageEditorEventHandler
        Public Event SelectUpload As System.EventHandler
        Public Event SelectFile As System.EventHandler
        Public Delegate Sub CropRectangleEventHandler(ByVal sender As Object, ByVal e As CropEventArgs)
        Public Event Cropped As CropRectangleEventHandler

#End Region

#Region "Constructors"
        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(ByVal ToolbarOrientation As System.Web.UI.WebControls.Orientation)
            Me.New()
            Me.ToolbarOrientation = ToolbarOrientation
        End Sub

        Public Sub New(ByVal ToolbarOrientation As System.Web.UI.WebControls.Orientation, ByVal PreviewSize As Size)
            Me.New()
            Me.ToolbarOrientation = ToolbarOrientation
            Me.PreviewSize = PreviewSize
        End Sub
#End Region

#Region "Public Properties"

        <ClientProperty()> _
        <Description("Provides the unique ID of the control to which client callbacks will flow. Normally " & _
                      "this will be the UniqueID of this control but in special circumstances may be set to direct " & _
                      "callbacks to a parent or sibling control.")> _
        <DefaultValue("")> _
        Public Property CallbackID() As String
            Get
                Return GetPropertyValue("CallbackID", Me.UniqueID)
            End Get
            Set(ByVal value As String)
                SetPropertyValue("CallbackID", value)
            End Set
        End Property

        <ClientProperty()> _
        <Description("When true, acceptance of crop or click of command button will generate a full postback " & _
                     "rather than a client callback."), DefaultValue(False)> _
        Public Property AutoPostBack() As Boolean
            Get
                GetPropertyValue("AutoPostBack", False)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("AutoPostBack", value)
            End Set
        End Property

        '<ClientProperty()> _
        '<DefaultValue(False)> _
        'Public Property UseWebService() As Boolean
        '    Get
        '        Return GetPropertyValue("UseWebService", False)
        '    End Get
        '    Set(ByVal value As Boolean)
        '        SetPropertyValue("UseWebService", value)
        '    End Set
        'End Property

        <ClientProperty()> _
        <Description("The animated progress bar or other image shown during image processing")> _
        <DefaultValue("images/progressbar.gif")> _
        Public Property WaitImageUrl() As String
            Get
                Return Page.ResolveUrl(GetPropertyValue("WaitImageUrl", "images/progressbar.gif"))
            End Get
            Set(ByVal value As String)
                SetPropertyValue("WaitImageUrl", value)
            End Set
        End Property

        <Category("Appearance"), DefaultValue(GetType(System.Web.UI.WebControls.Orientation), "Horizontal")> _
        <Description("Orientation of toolbar - vertically on left or horizontally on top")> _
        Public Property ToolbarOrientation() As System.Web.UI.WebControls.Orientation
            Get
                Return GetPropertyValue("ToolbarOrientation", System.Web.UI.WebControls.Orientation.Horizontal)
            End Get
            Set(ByVal value As System.Web.UI.WebControls.Orientation)
                SetPropertyValue("ToolbarOrientation", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("When true, show and allow the image file open button")> _
        Public Property AllowFileOpen() As Boolean
            Get
                Return GetPropertyValue("AllowFileOpen", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("AllowFileOpen", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("When true, show and allow the image file upload button")> _
        Public Property AllowFileUpload() As Boolean
            Get
                Return GetPropertyValue("AllowFileUpload", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("AllowFileUpload", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("When true, show and allow the image file save button")> _
        Public Property AllowFileSave() As Boolean
            Get
                Return GetPropertyValue("AllowFileSave", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("AllowFileSave", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("When true the file open button (if allowed) is enabled")> _
        Public Property EnableFileOpen() As Boolean
            Get
                Return GetPropertyValue("EnableFileOpen", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("EnableFileOpen", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("When true the file upload button (if allowed) is enabled")> _
        Public Property EnableFileUpload() As Boolean
            Get
                Return GetPropertyValue("EnableFileUpload", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("EnableFileUpload", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(False)> _
        <Description("When true the file save button (if allowed) is enabled")> _
        Public Property EnableFileSave() As Boolean
            Get
                Return GetPropertyValue("EnableFileSave", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("EnableFileSave", value)
            End Set
        End Property

        <Bindable(True), Category("Data"), DefaultValue(""), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _
        <PersistenceMode(PersistenceMode.Attribute), Description("Caption to show above preview image")> _
        Public Property Caption() As String
            Get
                Return GetPropertyValue("Caption", String.Empty)
            End Get
            Set(ByVal value As String)
                Dim oldvalue As String = Caption
                SetPropertyValue("Caption", value)
                'If value <> oldvalue Then UpdateCaption()
            End Set
        End Property

        <DefaultValue(GetType(System.Drawing.Size), "0,0"), TypeConverter(GetType(System.Drawing.SizeConverter))> _
        <Description("The size of the currently loaded image bitmap (Width, Height)")> _
        Public Property ImageSize() As Size
            Get
                If ClientState("ImageSize") Is Nothing Then
                    Return Size.Empty
                Else
                    Return CType(ClientState("ImageSize"), Size)
                End If
            End Get
            Set(ByVal value As Size)
                ClientState("ImageSize") = value
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(GetType(System.Drawing.Size), "0,0"), TypeConverter(GetType(System.Drawing.SizeConverter))> _
        <PersistenceMode(PersistenceMode.Attribute), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _
        <Description("The size of the preview image (Width, Height)")> _
        Public Property PreviewSize() As Size
            Get
                Return GetPropertyValue("PreviewSize", Defaults.PreviewSize)
            End Get
            Set(ByVal value As Size)
                SetPropertyValue("PreviewSize", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(GetType(System.Drawing.Point), "0,0"), TypeConverter(GetType(System.Drawing.PointConverter))> _
        <Description("The scale of the original image size to the preview image size (X, Y)")> _
        Public Property Scale() As Point
            Get
                Return GetPropertyValue("Scale", Point.Empty)
            End Get
            Set(ByVal value As Point)
                SetPropertyValue("Scale", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(GetType(System.Drawing.Size), "0,0"), TypeConverter(GetType(System.Drawing.SizeConverter))> _
        <Description("The minimum size of the crop area (Width, Height). Note that if minimum width or " & _
                      "height equals maximum width or height respectively, the crop rectangle width or height will be fixed.")> _
        Public Property MinCropSize() As Size
            Get
                Return GetPropertyValue("MinCropSize", Size.Empty)
            End Get
            Set(ByVal value As Size)
                SetPropertyValue("MinCropSize", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(GetType(System.Drawing.Size), "0,0"), TypeConverter(GetType(System.Drawing.SizeConverter))> _
        <Description("The maximum size of the crop area (Width, Height). Note that if minimum width or " & _
                      "height equals maximum width or height respectively, the crop rectangle width or height will be fixed.")> _
        Public Property MaxCropSize() As Size
            Get
                Return GetPropertyValue("MaxCropSize", Size.Empty)
            End Get
            Set(ByVal value As Size)
                SetPropertyValue("MaxCropSize", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(GetType(System.Drawing.Size), "0,0"), TypeConverter(GetType(System.Drawing.SizeConverter))> _
        <Description("If not empty, constrains crop area aspect ratio to Height/Width")> _
        Public Property AspectRatio() As Size
            Get
                Return GetPropertyValue("Aspect", Size.Empty)
            End Get
            Set(ByVal value As Size)
                SetPropertyValue("Aspect", value)
            End Set
        End Property

        <DefaultValue(GetType(System.Drawing.Rectangle), "0, 0, 0, 0")> _
        <TypeConverter(GetType(System.Drawing.Rectangle))> _
        <Description("Current coordinates and size of the crop rectangle.")> _
        Public Property CropRectangle() As Rectangle
            Get
                Return CType(ClientState("CropRectangle"), Rectangle)
            End Get
            Set(ByVal value As Rectangle)
                ClientState("CropRectangle") = value
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(True)> _
        <Description("Setting this to True (the default) will cause the various DIV elements of the " & _
                     "cropper to be attached when the image is loaded")> _
        Public Property AttachCropperOnLoad() As Boolean
            Get
                Return GetPropertyValue("AttachCropperOnLoad", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("AttachCropperOnLoad", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(False)> _
        <Description("Setting this to true will cause either a specified CropRectangle or the MinCropSize rectangle " & _
                       "to be displayed when the control is initialized.")> _
        Public Property ShowCropperOnInit() As Boolean
            Get
                Return GetPropertyValue("ShowCropperOnInit", False)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("ShowCropperOnInit", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(True)> _
        <Description("When set to true, a status bar showing the X, Y, Width and Height of the current CropRectangle " & _
                     "will be displayed below the preview image.")> _
        Public Property ShowStatusBar() As Boolean
            Get
                Return GetPropertyValue("ShowStatusBar", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("ShowStatusBar", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(True)> _
        <Description("When set to true, display the width x height of the currently loaded image in the StatusBar.")> _
        Public Property ShowImageSize() As Boolean
            Get
                Return GetPropertyValue("ShowImageSize", True)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("ShowImageSize", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue(False)> _
        <Description("When set to true, the escape key may be used to reset/hide the cropper, the arrow keys may be used " & _
                      "to move the crop rectangle 1 px. Holding down Shift key with arrow key moves 10 px. Holding down " & _
                      "Shift while resizing forces square crop area. Holding down Ctrl while resizing forces maintaining aspect " & _
                      "ratio of crop area at moment Ctrl first depressed.")> _
        Public Property CaptureKeys() As Boolean
            Get
                Return GetPropertyValue("CaptureKeys", False)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("CaptureKeys", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("Set to true to force the preview image to rescale up to the preview image frame " & _
                      "following an operation which results in an image smaller than the frame")> _
        Public Property Rescale() As Boolean
            Get
                If ClientState("Rescale") Is Nothing Then
                    Return True
                Else
                    Return Convert.ToBoolean(ClientState("Rescale"))
                End If
            End Get
            Set(ByVal value As Boolean)
                ClientState("Rescale") = value
            End Set
        End Property

        <ClientEvent()> _
        <DefaultValue("")> _
        <Description("Client-side handler to be called when crop area is accepted")> _
        Public Property EndCrop() As String
            Get
                Return GetPropertyValue("EndCrop", "")
            End Get
            Set(ByVal value As String)
                SetPropertyValue("EndCrop", value)
            End Set
        End Property

        <ClientEvent()> _
        <DefaultValue("")> _
        <Description("Client-side handler to be called at end of each callback")> _
        Public Property EndClientCallback() As String
            Get
                Return GetPropertyValue("EndClientCallback", "")
            End Get
            Set(ByVal value As String)
                SetPropertyValue("EndClientCallback", value)
            End Set
        End Property

        <ClientEvent("isDirtyChanged")> _
        <DefaultValue("")> _
        <Description("Client-side handler to be called at when IsDirty has changed")> _
        Public Property IsDirtyChanged() As String
            Get
                Return GetPropertyValue("IsDirtyChanged", "")
            End Get
            Set(ByVal value As String)
                SetPropertyValue("IsDirtyChanged", value)
            End Set
        End Property

        <ClientProperty()> _
        <DefaultValue("WESNetImageEditor_")> _
        <Description("Prefix to be added to CSS class names")> _
        Public Property ClassNamePrefix() As String
            Get
                Return GetPropertyValue("ClassNamePrefix", "WESNetImageEditor_")
            End Get
            Set(ByVal value As String)
                SetPropertyValue("ClassNamePrefix", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(True)> _
        <Description("Show current Filename if true")> _
        Public Property ShowFilename() As Boolean
            Get
                Return _ShowFilename
            End Get
            Set(ByVal value As Boolean)
                Dim oldvalue As Boolean = _ShowFilename
                _ShowFilename = value
                'If oldvalue <> value Then UpdateFilename()
            End Set
        End Property

        <Bindable(True), Category("Data"), DefaultValue(""), UrlProperty()> _
        <Description("The URL with which to set the src property of the preview image. " & _
                      "Normally, this is set automatically when a file or uploaded image stream is " & _
                      "is loaded into the Image property")> _
        Public Property ImageSrc() As String
            Get
                EnsureChildControls()
                Return _imgPreview.Src
            End Get

            Set(ByVal value As String)
                EnsureChildControls()
                _imgPreview.Src = value
            End Set
        End Property

        <Bindable(True), Category("Data"), DefaultValue("")> _
        <Description("The parent path of the file that is to be loaded")> _
        Public Property Parentpath() As String
            Get
                If ViewState("Parentpath") Is Nothing Then
                    If DesignMode Then
                        Return String.Empty
                    Else
                        Return Page.MapPath("")
                    End If
                Else
                    Return CType(ViewState("Parentpath"), String)
                End If
            End Get
            Set(ByVal value As String)
                ViewState("Parentpath") = value
            End Set
        End Property

        <Bindable(True), Category("Data"), DefaultValue("")> _
        <Description("The file name of the file that is loaded in the editor.")> _
        Public Property Filename() As String
            Get
                Return GetPropertyValue("Filename", String.Empty)
            End Get
            Set(ByVal value As String)
                Dim oldvalue As String = Filename
                SetPropertyValue("Filename", value)
                If Not String.IsNullOrEmpty(oldvalue) AndAlso oldvalue <> value Then
                    If AutoLoad Then
                        InvalidateImageCache()
                        IsDirty = False
                    End If
                    'If ShowFilename Then UpdateFilename()
                End If
            End Set
        End Property

        <Description("The full, resolved filepath of the image file"), Browsable(False)> _
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        Public ReadOnly Property ResolvedFilepath() As String
            Get
                If String.IsNullOrEmpty(Filename) Then
                    Return String.Empty
                Else
                    Return Path.Combine(Parentpath, Filename).Replace("\", "/")
                End If
            End Get
        End Property

        <Description("The image source type - F for file or U for upload")> _
        Public Property ImageSource() As String
            Get
                Return GetPropertyValue("ImageSource", String.Empty)
            End Get
            Set(ByVal value As String)
                If value Is Nothing OrElse (value = "F" OrElse value = "U" OrElse value = "") Then
                    SetPropertyValue("ImageSource", value)
                End If
            End Set
        End Property

        <Description("Cache temporary image data in memory or filesystem")> _
        <DefaultValue(GetType(CachePersistenceType), "Memory")> _
        Public Property CachePersistence() As CachePersistenceType
            Get
                If ClientState("CachePersistence") Is Nothing Then
                    Return CachePersistenceType.Memory
                Else
                    Return CType(ClientState("CachePersistence"), CachePersistenceType)
                End If
            End Get
            Set(ByVal value As CachePersistenceType)
                Dim oldvalue As CachePersistenceType = CachePersistence()
                If oldvalue <> value Then
                    Dim img As System.Drawing.Bitmap = Image
                    If Not img Is Nothing Then
                        InvalidateImageCache()
                        ClientState("CachePersistence") = value
                        Image = img
                    End If
                End If
            End Set
        End Property

        <ClientProperty(), DefaultValue(Defaults.CacheKeyPrefix)> _
        <Description("Cache Key Prefix - IMG for image, THM for thumbnail")> _
        Public Property CacheKeyPrefix() As String
            Get
                Return GetPropertyValue("CacheKeyPrefix", Defaults.CacheKeyPrefix)
            End Get
            Set(ByVal value As String)
                SetPropertyValue("CacheKeyPrefix", value)
            End Set
        End Property

        <DefaultValue("")> _
        <Description("When cache persistence = file, contains the relative folder path for cached images")> _
        Public Property CacheFolderpath() As String
            Get
                Return GetPropertyValue("CacheFolderpath", "")
            End Get
            Set(ByVal value As String)
                If Not HttpContext.Current Is Nothing And Not String.IsNullOrEmpty(value) Then
                    Dim MappedPath As String = HttpContext.Current.Server.MapPath(value).Replace("\", "/")
                    If Not Directory.Exists(MappedPath) Then
                        Directory.CreateDirectory(MappedPath)
                    End If
                End If
                SetPropertyValue("CacheFolderpath", value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(Defaults.CacheTime)> _
        <Description("Maximum time (sliding) image data is held in cache in seconds")> _
        Public Property CacheTime() As Double
            Get
                Return Convert.ToDouble(_CacheTime.Seconds)
            End Get
            Set(ByVal value As Double)
                _CacheTime = TimeSpan.FromSeconds(value)
            End Set
        End Property

        <Category("Behavior"), DefaultValue(False)> _
        <Description("If true, change of filename will invalidate the image cache " & _
                      "and raise the Reload event. The handler for this event should then read the " & _
                      "image file and set its bitmap into the Image property")> _
        Public Property AutoLoad() As Boolean
            Get
                Return GetPropertyValue("AutoLoad", False)
            End Get
            Set(ByVal value As Boolean)
                SetPropertyValue("AutoLoad", value)
            End Set
        End Property

        <Description("GUID string by which the cached image data of a particular instance of " & _
                      "this control will be retrieved."), Browsable(False)> _
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        Public ReadOnly Property ImageGuid() As String
            Get
                If _ImageGuid Is Nothing Then
                    If ViewState("ImageGuid") Is Nothing Then
                        _ImageGuid = Guid.NewGuid().ToString
                        ViewState("ImageGuid") = _ImageGuid
                    Else
                        _ImageGuid = CType(ViewState("ImageGuid"), String)
                    End If
                End If
                Return _ImageGuid
            End Get
        End Property

        'The following property is persisted on the HttpRuntime.Cache:
        <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        Public Property Image() As System.Drawing.Bitmap
            Get
                If _Image Is Nothing Then
                    _Image = GetCachedImage()
                End If
                If _Image Is Nothing AndAlso AutoLoad Then
                    RaiseEvent Reload(Me, New ImageEditorEventArgs(ImageGuid, ImageSource & "|" & ResolvedFilepath))
                End If
                Return _Image
            End Get
            Set(ByVal value As System.Drawing.Bitmap)
                If value Is Nothing Then
                    InvalidateImageCache()
                    ImageSize = Size.Empty
                Else
                    SetCachedImage(value)
                End If
                _Image = value
            End Set
        End Property

        <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        <Description("Returns true if the image in the cache has been modifed since it was first loaded")> _
        Public Property IsDirty() As Boolean
            Get
                If ClientState("IsDirty") Is Nothing Then
                    Return False
                Else
                    Return CType(ClientState("IsDirty"), Boolean)
                End If
            End Get
            Set(ByVal Value As Boolean)
                Dim oldvalue As Boolean = IsDirty
                ClientState("IsDirty") = Value
                'If Value <> oldvalue Then
                '    UpdateFilename()
                '    OnDirtyChanged(New System.EventArgs)
                'End If
            End Set
        End Property

        <Description("Relative path for the toolbar icons"), DefaultValue("")> _
        Public Property ToolbarIconPath() As String
            Get
                Return _ToolbarIconPath
            End Get
            Set(ByVal value As String)
                _ToolbarIconPath = value
            End Set
        End Property

        <Description("Generic list of the toolbar button definitions")> _
        Private ReadOnly Property Buttons() As Generic.List(Of ToolBarButton)
            Get
                If _ToolbarButtons Is Nothing Then
                    _ToolbarButtons = New Generic.List(Of ToolBarButton)
                End If
                Return _ToolbarButtons
            End Get
        End Property

        <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        <Description("Provides access to the Image server control representing the preview image")> _
        Public ReadOnly Property PreviewImage() As HtmlControls.HtmlImage
            Get
                EnsureChildControls()
                Return _imgPreview
            End Get
        End Property

        <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        Public ReadOnly Property ScriptManager() As System.Web.UI.ScriptManager
            Get
                EnsureScriptManager()
                Return _ScriptManager
            End Get
        End Property

#End Region

#Region "ClientState Implementation"

        Public ReadOnly Property ClientState() As Collections.Specialized.HybridDictionary
            Get
                If _ClientState Is Nothing Then
                    _ClientState = New Collections.Specialized.HybridDictionary
                End If
                Return _ClientState
            End Get
        End Property

        Protected ReadOnly Property ClientStateFieldID() As String
            Get
                If _ClientStateFieldID Is Nothing Then
                    _ClientStateFieldID = Me.ClientID & "_ClientState"
                End If
                Return _ClientStateFieldID
            End Get
        End Property

        Protected Sub LoadClientState(ByVal clientState As String)
            If String.IsNullOrEmpty(clientState) Then
                Me.ClientState.Clear()
            Else
                Dim js As New System.Web.Script.Serialization.JavaScriptSerializer()
                _ClientState = js.Deserialize(Of Collections.Specialized.HybridDictionary)(clientState)
            End If
        End Sub

        Protected Function SaveClientState() As String
            If ClientState.Count = 0 Then
                Return String.Empty
            Else
                Dim js As New System.Web.Script.Serialization.JavaScriptSerializer()
                Return js.Serialize(ClientState)
            End If
        End Function

        'Private Sub LoadClientStateValues()
        '    If Not String.IsNullOrEmpty(ClientStateFieldID) Then
        '        Dim csf As HiddenField = CType(NamingContainer.FindControl(ClientStateFieldID), HiddenField)
        '        If (Not csf Is Nothing) AndAlso (Not String.IsNullOrEmpty(csf.Value)) Then
        '            LoadClientState(csf.Value)
        '        End If
        '    End If
        'End Sub

#End Region

#Region "Friend Methods"
        Friend Function GetScriptReference() As String
            Return "$find('" & Me.ClientID & "')"
        End Function

        Friend Function GetScriptMethodReference(ByVal Method As String) As String
            Return "var iescript = " & GetScriptReference() & "; if (iescript) {iescript." & Method & ";}"
        End Function
#End Region

#Region "Overrides of ScriptControl"

        Protected Overrides ReadOnly Property TagKey() As System.Web.UI.HtmlTextWriterTag
            Get
                Return HtmlTextWriterTag.Div
            End Get
        End Property

        Public Overrides ReadOnly Property Controls() As System.Web.UI.ControlCollection
            Get
                EnsureChildControls()
                Return MyBase.Controls
            End Get
        End Property

        Protected Overrides Sub CreateChildControls()
            Controls.Clear()

            _CaptionBar = New HtmlControls.HtmlGenericControl("div")
            _CaptionBar.ID = "CaptionBar"
            Controls.Add(_CaptionBar)

            _FilenameBar = New HtmlControls.HtmlGenericControl("div")
            _FilenameBar.ID = "FilenameBar"
            Controls.Add(_FilenameBar)

            Buttons.Clear()
            Dim context As String = Me.GetScriptReference
            Dim successHandler As String = context & ".onSuccess"
            Dim errorHandler As String = context & ".onError"

            With Buttons
                .Add(New ToolBarButton(Me, "RotateRight", My.Resources.RotateRight_Cmd, context & ".processImage ('RotateRight'); return false;"))
                .Add(New ToolBarButton(Me, "RotateLeft", My.Resources.RotateLeft_Cmd, context & ".processImage ('RotateLeft'); return false;"))
                .Add(New ToolBarButton(Me, "FlipHoriz", My.Resources.FlipHoriz_Cmd, context & ".processImage('FlipHoriz'); return false;"))
                .Add(New ToolBarButton(Me, "FlipVert", My.Resources.FlipVert_Cmd, context & ".processImage('FlipVert'); return false;"))
                .Add(New ToolBarButton(Me, "Lighten", My.Resources.Lighten_Cmd, context & ".processImage('Lighten'); return false;"))
                .Add(New ToolBarButton(Me, "Darken", My.Resources.Darken_Cmd, context & ".processImage('Darken'); return false;"))
                .Add(New ToolBarButton(Me, "ContrastUp", My.Resources.ContrastUp_Cmd, context & ".processImage('ContrastUp'); return false;"))
                .Add(New ToolBarButton(Me, "ContrastDown", My.Resources.ContrastDown_Cmd, context & ".processImage('ContrastDown'); return false;"))
                .Add(New ToolBarButton(Me, "SaturationUp", My.Resources.SaturationUp_Cmd, context & ".processImage('SaturationUp'); return false;"))
                .Add(New ToolBarButton(Me, "SaturationDown", My.Resources.SaturationDown_Cmd, context & ".processImage('SaturationDown'); return false;"))
                If AllowFileOpen Then
                    _SelectFileButton = New ToolBarButton(Me, "SelectFile", My.Resources.SelectFile_Cmd) ', context & ".processImage('SelectFile'); return false;")
                    .Add(_SelectFileButton)
                End If
                If AllowFileUpload Then
                    _SelectUploadButton = New ToolBarButton(Me, "SelectUpload", My.Resources.SelectUpload_Cmd)
                    .Add(_SelectUploadButton)
                End If
                If AllowFileSave Then
                    _SaveImageButton = New ToolBarButton(Me, "SaveImage", My.Resources.SaveImage_Cmd)
                    .Add(_SaveImageButton)
                End If
            End With

            For i As Integer = 0 To Buttons.Count - 1
                Controls.Add(Buttons(i))
            Next
            _imgPreview = New HtmlControls.HtmlImage
            _imgPreview.ID = "imgPreview"
            Controls.Add(_imgPreview)
            ClearChildViewState()
            ChildControlsCreated = True
        End Sub

        Protected Overrides Sub AddAttributesToRender(ByVal writer As System.Web.UI.HtmlTextWriter)
            MyBase.AddAttributesToRender(writer)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "WESNetImageEditor")
            writer.AddStyleAttribute("position", "relative")
            writer.AddStyleAttribute("display", "table")
        End Sub

        Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
            MyBase.OnPreRender(e)
            EnsureID()
            Page.RegisterRequiresPostBack(Me)

            If Not _SelectFileButton Is Nothing Then
                _SelectFileButton.Enabled = EnableFileOpen
                _SelectFileButton.OnClientClick = Page.ClientScript.GetPostBackEventReference(Me, ImageGuid & "|SelectFile") & ";return false;"
            End If

            If Not _SelectUploadButton Is Nothing Then
                _SelectUploadButton.Enabled = EnableFileUpload
                _SelectUploadButton.OnClientClick = Page.ClientScript.GetPostBackEventReference(Me, ImageGuid & "|SelectUpload") & ";return false;"
            End If

            If Not _SaveImageButton Is Nothing Then
                _SaveImageButton.Enabled = EnableFileSave
                _SaveImageButton.OnClientClick = Page.ClientScript.GetPostBackEventReference(Me, ImageGuid & "|SaveImage") & ";return false;"
            End If

            Me.Width = Unit.Pixel(CalcWidthFromPreviewSize())
            Me.Height = Unit.Pixel(CalcHeightFromPreviewSize())
        End Sub

        Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter)
            EnsureChildControls()

            If Not Image Is Nothing Then
                ImageSrc = GenerateDynamicImageUrl(PreviewSize)
            End If

            ScriptManager.RegisterHiddenField(Me, ClientStateFieldID, SaveClientState())

            'If (Not _UpdateCaptionScript Is Nothing) Or (Not _UpdateFilenameScript Is Nothing) Then
            '    Dim UpdateScript As String = _UpdateCaptionScript & vbCrLf & _UpdateFilenameScript
            '    ScriptManager.RegisterStartupScript(Me, Me.GetType, "UpdateHeading", UpdateScript, True)
            '    _UpdateCaptionScript = Nothing
            '    _UpdateFilenameScript = Nothing
            'End If

            'If UseWebService Then
            '    Dim svc As New ServiceReference()
            '    svc.Path = "~/ImageProcessor.asmx"
            '    ScriptManager.Services.Add(svc)
            'End If
            
            _CaptionBar.Attributes.Add("class", ClassNamePrefix & "Caption")
            _FilenameBar.Attributes.Add("class", ClassNamePrefix & "Filename")

            With _imgPreview.Style
                .Add("margin-left", "0px auto")
                .Add("margin-right", "0px auto")
            End With

            If Caption.Length > 0 Then RenderHeaderRow(_CaptionBar, Caption, writer)
            If ShowFilename Then RenderHeaderRow(_FilenameBar, Filename & CStr(IIf(IsDirty, "*", "")), writer)
            RenderToolBar(writer)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, ClassNamePrefix & "Preview")
            writer.AddStyleAttribute("position", "relative")
            If ToolbarOrientation = Orientation.Vertical Then
                writer.AddStyleAttribute("float", "right")
                writer.AddStyleAttribute("clear", "right")
            Else
                writer.AddStyleAttribute("margin", "0px auto")
            End If
            writer.AddStyleAttribute("height", (PreviewSize.Height + StatusBarHeight).ToString & "px")
            writer.AddStyleAttribute("width", (PreviewSize.Width).ToString & "px")
            writer.AddStyleAttribute("text-align", "center")
            writer.RenderBeginTag(HtmlTextWriterTag.Div)
            If DesignMode Then
                With _imgPreview
                    .Src = Page.ClientScript.GetWebResourceUrl(Me.GetType, "black.gif")
                    .Width = PreviewSize.Width
                    .Height = PreviewSize.Height
                End With
            End If
            _imgPreview.RenderControl(writer)
            writer.RenderEndTag() '</div>
        End Sub

        Protected Overrides Function GetScriptDescriptors() As System.Collections.Generic.IEnumerable(Of System.Web.UI.ScriptDescriptor)
            Dim attr As ClientAttributeBase
            Dim ctl As System.Web.UI.Control

            If Not Visible Then Return Nothing
            Dim descriptors As New Generic.List(Of System.Web.UI.ScriptDescriptor)
            Dim scdesc As ScriptComponentDescriptor
            Dim name As String
            Dim value As Object
            Dim js As New System.Web.Script.Serialization.JavaScriptSerializer

            scdesc = New ScriptControlDescriptor("WESNet.AJAX.Controls.ImageEditorBehavior", ClientID)

            Dim props As PropertyDescriptorCollection = TypeDescriptor.GetProperties(Me)
            For Each prop As PropertyDescriptor In props
                attr = Nothing
                attr = CType(prop.Attributes(GetType(ClientPropertyAttribute)), ClientPropertyAttribute)
                If attr Is Nothing OrElse Not attr.IsApplied Then
                    attr = CType(prop.Attributes(GetType(ClientEventAttribute)), ClientEventAttribute)
                    If attr Is Nothing OrElse Not attr.IsApplied Then
                        attr = CType(prop.Attributes(GetType(ClientIDReferenceAttribute)), ClientIDReferenceAttribute)
                        If attr Is Nothing OrElse Not attr.IsApplied Then
                            Continue For
                        End If
                    End If
                End If
                If prop.ShouldSerializeValue(Me) Or prop.IsReadOnly Then
                    If String.IsNullOrEmpty(attr.ClientAlias) Then
                        name = prop.Name
                    Else
                        name = attr.ClientAlias
                    End If
                    value = prop.GetValue(Me)
                    If TypeOf attr Is ClientIDReferenceAttribute Or TypeOf attr Is ClientElementAttribute Then
                        ctl = FindControlRecursive(Me, CType(value, String))
                        If Not ctl Is Nothing Then
                            value = ctl.ClientID
                        End If
                    End If
                    If value Is Nothing Then Continue For
                    If TypeOf attr Is ClientPropertyAttribute Then
                        scdesc.AddProperty(name, value)
                    ElseIf TypeOf attr Is ClientEventAttribute Then
                        scdesc.AddEvent(name, CType(value, String))
                    ElseIf TypeOf attr Is ClientIDReferenceAttribute Then
                        scdesc.AddProperty(name, CType(value, String))
                    ElseIf TypeOf attr Is ClientElementAttribute Then
                        scdesc.AddElementProperty(name, CType(value, String))
                    End If
                End If
            Next
            If Not String.IsNullOrEmpty(ClientStateFieldID) Then
                scdesc.AddElementProperty("clientStateField", ClientStateFieldID)
            End If
            descriptors.Add(scdesc)
            Return descriptors

        End Function

        Protected Overrides Function GetScriptReferences() As System.Collections.Generic.IEnumerable(Of System.Web.UI.ScriptReference)
            If Not Visible Then Return Nothing
            Dim refs As New Generic.List(Of ScriptReference)
            Dim ref As ScriptReference = New ScriptReference("ImageEditorBehavior.js", Me.GetType.Assembly.FullName)
            refs.Add(ref)
            Return refs
        End Function
#End Region

#Region "Private Methods"

        'ViewState property get/set helpers

        Private Function GetPropertyValue(Of T)(ByVal PropertyName As String, ByVal DefaultValue As T) As T
            If ViewState(PropertyName) Is Nothing Then
                Return DefaultValue
            Else
                Return CType(ViewState(PropertyName), T)
            End If
        End Function

        Private Sub SetPropertyValue(Of T)(ByVal PropertyName As String, ByVal Value As T)
            ViewState(PropertyName) = Value
        End Sub

        Private Sub EnsureScriptManager()
            If _ScriptManager Is Nothing And Not DesignMode Then
                _ScriptManager = System.Web.UI.ScriptManager.GetCurrent(Me.Page)
                If _ScriptManager Is Nothing Then
                    Throw New HttpException("No ScriptManager on page as required by ImageEditor script control")
                End If
            End If
        End Sub

        Private Function RectangleParse(ByVal s As String) As Rectangle
            Dim r As Rectangle = New Rectangle(0, 0, 0, 0)
            If Not String.IsNullOrEmpty(s) Then
                Dim values() As String = Regex.Replace(s, "[^0-9,]", "").Split(","c)
                Dim IsValid As Boolean
                For i As Integer = 0 To values.Length - 1
                    If Not String.IsNullOrEmpty(values(i)) Then
                        IsValid = True
                    End If
                Next
                If IsValid Then
                    r = New Rectangle(Integer.Parse(values(0)), Integer.Parse(values(1)), _
                                           Integer.Parse(values(2)), Integer.Parse(values(3)))
                End If
            End If
            Return r
        End Function

        'Private Sub UpdateCaption()
        'Dim content As String
        'If Caption.Length > 0 Then
        '    content = Caption
        'Else
        '    content = "&nbsp;"
        'End If
        '_UpdateCaptionScript = String.Format("$get('{0}_CaptionBar').innerHtml='{1}';", Me.ClientID, content)
        'End Sub

        'Private Sub UpdateFilename()
        'Dim content As String
        'If ShowFilename AndAlso Filename.Length > 0 Then
        '    content = Filename & CStr(IIf(IsDirty, "*", ""))
        'Else
        '    content = "&nbsp;"
        'End If
        '_UpdateFilenameScript = String.Format("$get('{0}_FilenameBar').innerHtml='{1}';", Me.ClientID, content)
        'End Sub

        Private Function GetCacheFileName(ByVal CacheKey As String) As String
            If DesignMode Then
                Return String.Empty
            Else
                If String.IsNullOrEmpty(CacheKey) Then
                    Throw New System.ArgumentNullException("Cache key cannot be null or empty")
                End If
                If String.IsNullOrEmpty(CacheFolderpath) Then
                    Throw New System.IO.IOException("Cache folder path cannot be null or empty")
                End If
                Return HttpContext.Current.Server.MapPath(Path.Combine(CacheFolderpath, CacheKey & ".resource").Replace("\", "/"))
            End If
        End Function

        Private Sub DeleteCacheFile(ByVal Filename As String)
            If Not String.IsNullOrEmpty(Filename) Then File.Delete(Filename)
        End Sub

        Private Sub RemoveCachedImage(ByVal CacheKey As String)
            Dim Filename As String = GetCacheFileName(CacheKey)
            DeleteCacheFile(Filename)
        End Sub

        Private Function GetCachedImage() As System.Drawing.Bitmap
            If HttpContext.Current Is Nothing Then Return ImageUtilities.CreateBlankImage(PreviewSize, Color.Black)
            Dim CacheKey As String = CacheKeyPrefix & ImageGuid
            Dim obj As Object = Nothing
            Dim fs As FileStream = Nothing
            Dim img As System.Drawing.Bitmap = Nothing
            Dim imgCopy As System.Drawing.Bitmap = Nothing
            Dim g As System.Drawing.Graphics = Nothing
            Select Case CachePersistence
                Case CachePersistenceType.Memory
                    obj = HttpRuntime.Cache(CacheKey)
                Case CachePersistenceType.File
                    Dim CacheFilepath As String = GetCacheFileName(CacheKey)
                    If File.Exists(CacheFilepath) Then
                        Try
                            fs = New FileStream(CacheFilepath, FileMode.Open, FileAccess.Read)
                            img = New Bitmap(fs)
                            'Note: When a bitmap image is created from a file or stream, the file or stream remains
                            'locked until the image object is disposed preventing saving of the modified image back to the
                            'same file. If the file is locked, Bitmap.Save() will throw the error "A generic occurred in GDI+."
                            'By drawing the image opened from the file to a new image of the same size and format, we
                            'can then dispose of the original image bitmap and release the file lock.
                            'Since we cannot create a Graphics object from an image having an Indexed pixel format (gif)
                            'we use Format24bppRgb instead.

                            If Not img Is Nothing Then
                                Dim pixelFormat As Drawing.Imaging.PixelFormat = img.PixelFormat
                                If (pixelFormat And Drawing.Imaging.PixelFormat.Indexed) <> 0 Then
                                    pixelFormat = Drawing.Imaging.PixelFormat.Format24bppRgb
                                End If
                                imgCopy = New System.Drawing.Bitmap(img.Width, img.Height, pixelFormat)
                                g = System.Drawing.Graphics.FromImage(imgCopy)
                                g.InterpolationMode = Drawing.Drawing2D.InterpolationMode.HighQualityBicubic
                                g.DrawImage(img, 0, 0)
                                obj = imgCopy
                            End If
                        Catch ex As Exception
                            DeleteCacheFile(CacheFilepath)
                        Finally
                            If Not fs Is Nothing Then fs.Close()
                            fs = Nothing
                            If Not g Is Nothing Then g.Dispose()
                            If Not img Is Nothing Then img.Dispose()
                            img = Nothing
                        End Try
                    End If
            End Select
            If Not obj Is Nothing Then
                imgCopy = CType(obj, Bitmap)
                ImageSize = New Size(imgCopy.Width, imgCopy.Height)
            End If
            Return imgCopy
        End Function

        Private Sub SetCachedImage(ByVal img As System.Drawing.Bitmap)
            If img Is Nothing Or DesignMode Then Exit Sub
            Dim CacheKey As String = CacheKeyPrefix & ImageGuid
            Select Case CachePersistence
                Case CachePersistenceType.Memory
                    HttpRuntime.Cache.Insert(CacheKey, img, Nothing, DateTime.MaxValue, _CacheTime, Caching.CacheItemPriority.AboveNormal, Nothing)
                Case CachePersistenceType.File
                    Dim CacheFilepath As String = GetCacheFileName(CacheKey)
                    If File.Exists(CacheFilepath) Then
                        File.Delete(CacheFilepath)
                    End If
                    img.Save(CacheFilepath, System.Drawing.Imaging.ImageFormat.MemoryBmp)
            End Select
            ImageSize = New Size(img.Width, img.Height)
        End Sub

        Private Sub RenderHeaderRow(ByVal ctrl As HtmlControls.HtmlGenericControl, ByVal Text As String, ByVal writer As HtmlTextWriter)
            With ctrl.Style
                .Add("position", "relative")
                .Add("width", PreviewSize.Width.ToString & "px")
                If ToolbarOrientation = Orientation.Vertical Then
                    .Add("left", VerticalToolBarWidth().ToString & "px")
                Else
                    .Add("margin", "0px auto")
                End If
                .Add("clear", "right")
            End With
            ctrl.InnerText = Text
            ctrl.RenderControl(writer)
        End Sub

        Private Function StatusBarHeight() As Integer
            If ShowStatusBar Then
                Return 25
            Else
                Return 0
            End If
        End Function

        Private Function VerticalToolBarWidth() As Integer
            Return (ToolBarButton.ButtonFrameWidth * CInt(Math.Ceiling((Buttons.Count * ToolBarButton.ButtonFrameHeight) / PreviewSize.Height)))
        End Function

        Private Function HorizontalToolBarHeight() As Integer
            Return (ToolBarButton.ButtonFrameHeight * CInt(Math.Ceiling((Buttons.Count * ToolBarButton.ButtonFrameWidth) / PreviewSize.Width)) + ToolBarButton.ButtonPadding - 1)
        End Function

        Private Sub RenderToolBar(ByVal writer As HtmlTextWriter)
            writer.AddAttribute("class", ClassNamePrefix & "ToolBar")
            If ToolbarOrientation = Orientation.Vertical Then
                Dim TotalWidth As Integer = VerticalToolBarWidth()
                writer.AddStyleAttribute("height", (PreviewSize.Height).ToString & "px")
                writer.AddStyleAttribute("width", TotalWidth.ToString & "px")
                writer.AddStyleAttribute("position", "relative")
                writer.AddStyleAttribute("float", "left")
                writer.AddStyleAttribute("clear", "left")
                writer.RenderBeginTag(HtmlTextWriterTag.Div)

                Dim j As Integer = ToolBarButton.ButtonPadding
                For i As Integer = 0 To Buttons.Count - 1
                    Buttons(i).RenderControl(writer)
                    j += ToolBarButton.ButtonFrameWidth
                    If j >= TotalWidth Then
                        writer.Write("<br />")
                        j = ToolBarButton.ButtonPadding
                    End If
                Next
            Else
                writer.AddStyleAttribute("position", "relative")
                writer.AddStyleAttribute("width", PreviewSize.Width.ToString & "px")
                writer.AddStyleAttribute("height", HorizontalToolBarHeight.ToString & "px")
                writer.AddStyleAttribute("margin", "0px auto")
                writer.AddStyleAttribute("clear", "right")
                writer.RenderBeginTag(HtmlTextWriterTag.Div)
                For i As Integer = 0 To Buttons.Count - 1
                    Buttons(i).RenderControl(writer)
                Next
            End If
            writer.RenderEndTag() '</div>
        End Sub

        Private Function FindControlRecursive(ByVal ctl As Control, ByVal ID As String) As Control
            If ctl Is Nothing Or String.IsNullOrEmpty(ID) Then Return Nothing
            Dim c As Control
            c = ctl.FindControl(ID)
            If c Is Nothing Then
                ctl = ctl.NamingContainer
                If Not ctl Is Nothing Then Return FindControlRecursive(ctl, ID)
            End If
            Return c
        End Function

#End Region

#Region "Protected and Friend Methods"

        Protected Overridable Sub OnDirtyChanged(ByVal e As EventArgs)
            RaiseEvent DirtyChanged(Me, e)
        End Sub

        Protected Overridable Sub OnSelectFile(ByVal e As EventArgs)
            RaiseEvent SelectFile(Me, e)
        End Sub

        Protected Overridable Sub OnSelectUpload(ByVal e As EventArgs)
            RaiseEvent SelectUpload(Me, e)
        End Sub

        Protected Overridable Sub OnSaveImage(ByVal e As ImageEditorEventArgs)
            RaiseEvent SaveImage(Me, e)
        End Sub

        Protected Sub OnCropped(ByVal e As CropEventArgs)
            RaiseEvent Cropped(Me, e)
        End Sub

        Friend Function CalcWidthFromPreviewSize() As Integer
            If ToolbarOrientation = Orientation.Horizontal Then
                Return PreviewSize.Width + 2
            Else
                Return PreviewSize.Width + VerticalToolBarWidth() + 10
            End If
        End Function

        Friend Function CalcHeightFromPreviewSize() As Integer
            Dim h As Integer = PreviewSize.Height + StatusBarHeight()
            If Not String.IsNullOrEmpty(Caption) Then h += 25
            If ShowFilename Then h += 25
            If ToolbarOrientation = Orientation.Horizontal Then
                h += HorizontalToolBarHeight()
            End If
            Return h
        End Function

        'For later use when design time rendering implemented
        Friend Sub SetPreviewSizeFromControlSize()
            Dim heightOffset As Integer = 0

            If Not String.IsNullOrEmpty(Caption) Then heightOffset += 25
            If ShowFilename AndAlso Not String.IsNullOrEmpty(Filename) Then heightOffset += 25

            Select Case ToolbarOrientation
                Case Orientation.Horizontal
                    PreviewSize = New Size(CInt(Me.Width.Value) - 2, CInt(Me.Height.Value) - heightOffset - HorizontalToolBarHeight() - StatusBarHeight())
                Case Orientation.Vertical
                    PreviewSize = New Size(CInt(Me.Width.Value) - VerticalToolBarWidth() - 4, CInt(Me.Height.Value) - heightOffset - StatusBarHeight())
            End Select
        End Sub


#End Region

#Region "Public Methods"

        'Generates an image tag url using the DynamicImage HttpHandler
        Public Function GenerateDynamicImageUrl(ByVal maxSize As Size) As String
            Dim appPath As String = HttpContext.Current.Request.ApplicationPath.TrimEnd("/"c) & "/"
            Dim url As String = String.Empty
            Select Case CachePersistence
                Case CachePersistenceType.Memory
                    url = String.Format("{0}DynamicImage.ashx?guid={1}&maxwidth={2}&maxheight={3}&rescale={4}&v={5}", _
                             appPath, CacheKeyPrefix & ImageGuid, maxSize.Width, maxSize.Height, Rescale, Now().Ticks)
                Case CachePersistenceType.File
                    url = String.Format("{0}DynamicImage.ashx?path={1}&guid={2}&maxwidth={3}&maxheight={4}&rescale={6}&v={6}", _
                             appPath, Context.Server.UrlEncode(CacheFolderpath), CacheKeyPrefix & ImageGuid, maxSize.Width, maxSize.Height, Rescale, Now().Ticks)
            End Select
            Return url
        End Function

        Public Sub InvalidateImageCache()
            If Not HttpContext.Current Is Nothing Then
                Select Case CachePersistence
                    Case CachePersistenceType.Memory
                        HttpRuntime.Cache.Remove(CacheKeyPrefix & ImageGuid)
                    Case CachePersistenceType.File
                        RemoveCachedImage(CacheKeyPrefix & ImageGuid)
                End Select
            End If
        End Sub
#End Region

#Region "Event Handlers"

        Protected Sub ImageEditor_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

            Dim CSSLinkID As String = "WESNetImageEditor"
            Dim CSSLinkHref As String = Page.ClientScript.GetWebResourceUrl(Me.GetType(), "WESNetImageEditor.css")
            If Not String.IsNullOrEmpty(CSSLinkHref) Then
                If Page.Header.FindControl(CSSLinkID) Is Nothing Then
                    Dim styleLink As New HtmlControls.HtmlLink
                    With styleLink
                        .ID = CSSLinkID
                        .Attributes("rel") = "stylesheet"
                        .Attributes("type") = "text/css"
                        .Href = CSSLinkHref
                    End With
                    Page.Header.Controls.Add(styleLink)
                End If
            End If

            'This should not be necessary but seems to be to register the callback on the client side. FMI see
            'AJAXControlToolkit.Rating.cs as example

            'If Not DesignMode Then
            '    Dim csm As ClientScriptManager = Page.ClientScript
            '    csm.GetCallbackEventReference(Me, "", "", "")
            'End If
            'WaitImageUrl = Page.ClientScript.GetWebResourceUrl(Me.GetType, "loading.gif")

        End Sub

        Protected Function GetCallbackResult() As String Implements ICallbackEventHandler.GetCallbackResult
            Return _CallbackResult
        End Function

        Protected Sub RaiseCallbackEvent(ByVal eventArgument As String) Implements ICallbackEventHandler.RaiseCallbackEvent
            Dim jsonSerializer As New System.Web.Script.Serialization.JavaScriptSerializer
            Dim args() As String = eventArgument.Split("|"c)
            _ImageGuid = args(0)

            Dim ClientStateValues As String() = Page.Request.Form.GetValues(ClientStateFieldID)
            If (ClientStateValues.Length > 0) AndAlso Not String.IsNullOrEmpty(ClientStateValues(0)) Then
                LoadClientState(ClientStateValues(0))
            End If

            If Image Is Nothing Then
                'Throw New ArgumentNullException("Image", "Cached image data has expired.")
                Exit Sub
            End If
            Dim img As System.Drawing.Bitmap
            Dim _Command As String = ""
            If args(1) = "Crop" Then
                Dim cropRect As Rectangle = RectangleParse(args(2))
                OnCropped(New CropEventArgs(cropRect))
                img = ImageUtilities.Crop(Image, cropRect)
                _Command = "Reset"
            Else
                img = New Bitmap(Image)
                Select Case args(1)
                    Case "RotateLeft"
                        ImageUtilities.RotateFlip(img, RotateFlipType.Rotate270FlipNone)
                        _Command = "Reset"
                    Case "RotateRight"
                        ImageUtilities.RotateFlip(img, RotateFlipType.Rotate90FlipNone)
                        _Command = "Reset"
                    Case "FlipHoriz"
                        ImageUtilities.RotateFlip(img, RotateFlipType.RotateNoneFlipX)
                    Case "FlipVert"
                        ImageUtilities.RotateFlip(img, RotateFlipType.RotateNoneFlipY)
                    Case "Lighten"
                        ImageUtilities.AdjustBrightness(img, 5)
                    Case "Darken"
                        ImageUtilities.AdjustBrightness(img, -5)
                    Case "ContrastUp"
                        ImageUtilities.AdjustContrast(img, 3)
                    Case "ContrastDown"
                        ImageUtilities.AdjustContrast(img, -3)
                    Case "SaturationUp"
                        ImageUtilities.AdjustSaturation(img, 5)
                    Case "SaturationDown"
                        ImageUtilities.AdjustSaturation(img, -5)
                    Case Else
                        Throw New ArgumentException("Command named " & eventArgument & " is not valid.")
                End Select
            End If
            If Not img Is Nothing Then
                IsDirty = True
                Image = img
            End If
            _CallbackResult = jsonSerializer.Serialize(New ProcessImageResult(_Command, ImageSize, IsDirty))
        End Sub
#End Region

#Region "IPostBackEventHandler Implementation"
        Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent
            Dim args() As String = eventArgument.Split("|"c)
            _ImageGuid = args(0)
            Select Case args(1)
                Case "SelectFile"
                    OnSelectFile(New System.EventArgs())
                Case "SelectUpload"
                    OnSelectUpload(New System.EventArgs())
                Case "SaveImage"
                    OnSaveImage(New ImageEditorEventArgs(ImageGuid, "SAVE|" & ResolvedFilepath))
                Case "DirtyChanged"
                    OnDirtyChanged(New System.EventArgs())
            End Select
        End Sub
#End Region

#Region "IPostBackDataHandler Implementation"
        Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData
            Dim ClientStateValue As String = postCollection(ClientStateFieldID)
            If Not String.IsNullOrEmpty(ClientStateValue) Then
                LoadClientState(ClientStateValue)
            End If
            Return False
        End Function

        Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent
            'Not Implemented - not needed
        End Sub
#End Region

    End Class

    Public Class ImageEditorEventArgs
        Inherits EventArgs

        Private _Commands As String
        Private _ImageGuid As String

        Public Sub New(ByVal ImageGuid As String, ByVal Commands As String)
            _Commands = Commands
            _ImageGuid = ImageGuid
        End Sub

        Public ReadOnly Property Commands() As String
            Get
                Return _Commands
            End Get
        End Property

        Public ReadOnly Property ImageGuid() As String
            Get
                Return _ImageGuid
            End Get
        End Property
    End Class

    Public Class CropEventArgs
        Inherits System.EventArgs
        Private _CropRectangle As Rectangle
        Private _CallBackResult As String

        Public Sub New(ByVal rect As Rectangle)
            _CropRectangle = rect
        End Sub

        Public Property CropRectangle() As Rectangle
            Get
                Return _CropRectangle
            End Get
            Set(ByVal value As Rectangle)
                _CropRectangle = value
            End Set
        End Property

        Public Property CallBackResult() As String
            Get
                Return _CallBackResult
            End Get
            Set(ByVal value As String)
                _CallBackResult = value
            End Set
        End Property
    End Class

    Public Class ProcessImageResult
        Private _Command As String = ""
        Private _ImageSize As Size
        Private _IsDirty As Boolean

        Public Sub New(ByVal Command As String, ByVal ImageSize As Size, ByVal IsDirty As Boolean)
            If Not Command Is Nothing Then _Command = Command
            _ImageSize = ImageSize
            _IsDirty = IsDirty
        End Sub

        Public ReadOnly Property Command() As String
            Get
                Return _Command
            End Get
        End Property

        Public ReadOnly Property ImageSize() As Size
            Get
                Return _ImageSize
            End Get
        End Property

        Public ReadOnly Property IsDirty() As Boolean
            Get
                Return _IsDirty
            End Get
        End Property
    End Class
End Namespace